package org.sakai.service;

import org.sakai.dto.EmailDTO;
import org.sakai.entity.ErrorCode;
import org.sakai.entity.Result;
import org.sakai.entity.User;
import org.sakai.entity.vo.UserVO;
import org.sakai.mapper.UserMapper;
import org.sakai.util.EmailUtil;
import org.sakai.util.UserUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.sakai.util.PBKDF2Util.*;
import static org.sakai.util.UserUtils.*;

@Service
@Transactional
public class UserService {
    @Autowired
    UserMapper userMapper;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    EmailUtil emailUtil;
    @Autowired
    MinioService minioService;

    /**
     * 通过邮箱创建用户
     * @param email
     * @param password
     * @return
     */
    @Transactional
    public Result<String> createUserByEmail(String email, String password, String verifyCode){
        //格式检查
        if(!checkEmail(email)){return Result.error(ErrorCode.EMAIL_FORMAT_ERROR);}
        if(!checkPassword(password)){return Result.error(ErrorCode.PASSWORD_FORMAT_ERROR);}
        //获取存储在redis中的邮箱验证码,可能返回null值
        Object code = redisTemplate.opsForValue().get("VerifyCode:" + email);
        //不一致，或者验证码失效，则返回错误
        if(!verifyCode.equals(code)) {return Result.error(ErrorCode.VERIFY_CODE_ERROR);}
        //生成用户信息
        String uuid = UUID.randomUUID().toString();
        try {
            //生成密码盐值，对密码进行盐值加密
            String salt = generateSalt();
            String encryptedPassword = getEncryptedPassword(password, salt);
            User user = new User(uuid, getRandomUserName(), email, null, encryptedPassword, salt);
            int i = userMapper.insert(user);
            if(i != 0){
                return Result.success(null);
            }
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            return Result.error(ErrorCode.ENCRYPTED_ERROR);
        }
        return Result.error(ErrorCode.INSERT_ERROR);
    }

    /**
     * 通过电话创建用户
     * @param phone
     * @param password
     * @return
     */
    @Transactional
    public Result<String> createUserByPhone(String phone, String password, String verifyCode){
        if(!checkPhone(phone)){return Result.error(ErrorCode.PHONE_FORMAT_ERROR);}
        if(!checkPassword(password)){return Result.error(ErrorCode.PASSWORD_FORMAT_ERROR);}
        //TODO:手机验证码验证功能
        String uuid = UUID.randomUUID().toString();
        try {
            String salt = generateSalt();
            String encryptedPassword = getEncryptedPassword(password, salt);
            User user = new User(uuid, getRandomUserName(), null, phone, encryptedPassword, salt);
            int i = userMapper.insert(user);
            if(i != 0){return Result.success(null);}
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            return Result.error(ErrorCode.ENCRYPTED_ERROR);
        }
        return Result.error(ErrorCode.INSERT_ERROR);
    }

    /**
     * 通过邮箱与密码登录
     * @param email
     * @param password
     * @return
     */
    public Result<UserVO> loginByEmailAndPassword(String email, String password){
        //通过邮箱获得用户
        User user = userMapper.selectByEmail(email);
        if(user == null){return Result.error(ErrorCode.USER_NOT_FOUND_ERROR);}
        try {
            //检查密码是否正确
            boolean authenticate = authenticate(password, user.getPassword(), user.getSalt());
            if(authenticate){
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("uid", user.getId());
                String token = UserUtils.createToken(map);
                //缓存token，7天过时
                redisTemplate.opsForValue().set("jwt:" + user.getId(), token, 7, TimeUnit.DAYS);
                return Result.success(new UserVO(user.getId(), user.getUsername(), user.getEmail(), token, null));
            }else{
                return Result.error(ErrorCode.PASSWORD_ERROR);
            }
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 通过电话号码与密码登录
     * @param phone
     * @param password
     * @return
     */
    public Result<String> loginByPhoneAndPassword(String phone, String password){
        User user = userMapper.selectByPhone(phone);
        if(user == null){return Result.error(ErrorCode.USER_NOT_FOUND_ERROR);}
        try {
            boolean authenticate = authenticate(password, user.getPassword(), user.getSalt());
            if(authenticate){
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("uid", user.getId());
                String token = UserUtils.createToken(map);
                redisTemplate.opsForValue().set("jwt:" + user.getId(), token, 7, TimeUnit.DAYS);
                return Result.success(token);
            }else{
                return Result.error(ErrorCode.PASSWORD_ERROR);
            }
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 通过邮箱验证码登录
     * @param email
     * @param verifyCode
     * @return token
     */
    public Result<UserVO> loginByEmailAndVerifyCode(String email, String verifyCode){
        //从数据库索引对应用户信息
        User user = userMapper.selectByEmail(email);
        //用户找不到
        if(user == null){return Result.error(ErrorCode.USER_NOT_FOUND_ERROR);}
        String code = (String) redisTemplate.opsForValue().get("VerifyCode:" + email);
        //验证码错误或者已经过期
        if(!verifyCode.equals(code)){return Result.error(ErrorCode.VERIFY_CODE_ERROR);}
        // 删除验证码
        redisTemplate.delete("VerifyCode:" + email);
        //生成登录token
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("uid", user.getId());
        String token = UserUtils.createToken(map);
        //redis缓存登录token，7天过时
        redisTemplate.opsForValue().set("jwt:" + user.getId(), token, 7, TimeUnit.DAYS);
        return Result.success(new UserVO(user.getId(), user.getUsername(), user.getEmail(), token, null));
    }

    /**
     * 通过短信验证码登录
     * @param phone
     * @param verifyCode
     * @return token
     */
    public Result<String> loginByPhoneAndVerifyCode(String phone, String verifyCode){
        User user = userMapper.selectByPhone(phone);
        if(user == null){return Result.error(ErrorCode.USER_NOT_FOUND_ERROR);}
        String code = (String) redisTemplate.opsForValue().get("VerifyCode:" + phone);
        if(code == null){return Result.error(ErrorCode.VERIFY_CODE_ERROR);}
        if(!code.equals(verifyCode)){return Result.error(ErrorCode.VERIFY_CODE_ERROR);}
        //验证成功后删除验证码
        redisTemplate.delete("VerifyCode:" + phone);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("uid", user.getId());
        String token = UserUtils.createToken(map);
        redisTemplate.opsForValue().set("jwt:" + user.getId(), token, 7, TimeUnit.DAYS);
        return Result.success(token);
    }

    /**
     * 返送邮箱验证码，同时将验证码存入redis
     * 验证码在登录与注册使用的是同一个
     * @param dstEmail
     * @return
     */
    public Result<String> sendEmailCode(String dstEmail) {
        //检查之前验证码是否已经发送过，发送过未过期可以选择重发，重发时间太短拒绝重发，考虑成本
        if(Objects.equals(redisTemplate.opsForValue().get("VerifyCodeBlacklist:" + dstEmail), 1)){
            return Result.error(ErrorCode.VERIFY_CODE_TOO_FREQUENT_ERROR);
        }
        //生成验证码
        Map<String, Object> map = new HashMap<>();
        //存储验证码
        String verifyCode = emailUtil.generateVerificationCode();
        //这里需要字符数组
        map.put("verifyCode", verifyCode.toCharArray());
        EmailDTO emailDTO = EmailDTO.builder()
                .template("common")
                .email(dstEmail)
                .subject("验证码")
                .commentMap(map).build();
        try {
            emailUtil.sendHtmlMail(emailDTO);
        } catch (Exception e) {
            return Result.error(ErrorCode.UNKNOWN_ERROR);
        }
        //验证码5分钟失效,验证码存入redis
        redisTemplate.opsForValue().set("VerifyCode:" + dstEmail, verifyCode, 5, TimeUnit.MINUTES);
        //设置1分钟内不能重发黑名单
        redisTemplate.opsForValue().set("VerifyCodeBlacklist:" + dstEmail, 1, 1, TimeUnit.MINUTES);
        //只返回成功，不返回验证码
        return Result.success("success");
    }

    /**
     * 发送短信验证码，同时将验证码存入redis
     * @param phone
     * @return
     */
    public Result<String> sendPhoneCode(String phone) {
        final int length = 6;
        StringBuilder captcha = new StringBuilder();
        Random random = new Random();

        for (int i = 0; i < length; i++) {
            int digit = random.nextInt(10); // 0-9
            captcha.append(digit);
        }
        //TODO:添加短信发送功能
        redisTemplate.opsForValue().set("VerifyCode:" + phone, captcha.toString(), 5, TimeUnit.MINUTES);
        return Result.success("success");
    }

    public Result<UserVO> getUserInfo(String id) {
        User user = userMapper.selectByPrimaryKey(id);
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return Result.success(userVO);
    }
}
